<!-- 文件上传组件 -->
<template>
	<view>
		<uni-file-picker v-model="fileData" fileMediatype="all" :limit="limit" mode="grid" @select="selectFile"
			:auto-upload="false" @progress="progress" @success="success" @delete="deleteFile" @fail="fail">
			<slot name="uploadDom"></slot>
		</uni-file-picker>
		<uni-popup ref="fileProgressPopup" type="center" :animation="false" :is-mask-click="false">
			<view class="progress-box">
				<view id="text">
					<text style="--i:1">文</text>
					<text style="--i:2">件</text>
					<text style="--i:3">上</text>
					<text style="--i:4">传</text>
					<text style="--i:5">中</text>
					<text style="--i:6">.</text>
					<text style="--i:7">.</text>
					<text style="--i:8">.</text>
				</view>
				<view>
					<progress :percent="fileInfo.progress" show-info stroke-width="10" />
				</view>
			</view>
		</uni-popup>
	</view>
</template>

<script>
	import {
		uploadFile, // 单文件上传方法
		uploadFileSlice, // 文件分片上传方法
		uploadFileMerge // 合并分片文件方法
	} from '@/utils/request.js'
	import SparkMD5 from 'spark-md5'
	import {
		queryApplicationUploadConfigByAppCodeApi, // 查询文件分片上传配置接口
		queryCurrentUploadResultByFileMd5Api, // 查询文件状态接口
		initUploadRegisterRecordApi, // 初始化文件接口
	} from '@/api/common';
	export default {
		props: {
			// 最大文件上传数
			limit: {
				type: Number,
				default: 1
			}
		},
		data() {
			return {
				fileData: [],
				fileInfo: {
					
					fileName: '',
					fileSize: '',
					file: null, // 上传的文件
					progress: 0, // ui显示的进度值
					// 文件hash
					fileHash: '',
				},
				chunkSize: null, // 分片大小
				maxSize: null, // 直传文件最大50m 超过则分片
				sliceSwitch: 0, // 是否开启分片上传（分片功能启用开关，0开启，1关闭）
			}
		},
		async mounted() {
			await this.getFileConfig()
		},
		methods: {
			// 文件切片
			async section(file, fileInfo) {
				const fileArr = await queryCurrentUploadResultByFileMd5Api(this.fileInfo.fileHash)
				console.log(fileArr, 'fileArr')
				if (!fileArr.success) {
					return
				}
				// exitPartList 已上传分片数组 totalSlices总片数 sliceSize 每片大小
				const exitPartList = fileArr.data.chunkUploadedList
				const totalSlices = fileArr.data.chunkNum
				const sliceSize = fileArr.data.chunkSize

				// 总片数和当前上传片数一致说明分片都上传完成了，直接合并
				if (exitPartList.length == totalSlices) {
					const mergeData = await uploadFileMerge(this.fileInfo.fileHash)
					return mergeData
				}
				// 大文件才显示上传进度条
				this.$refs.fileProgressPopup.open()
				// 当前第几个切片
				const startChunks = exitPartList.length ? exitPartList[exitPartList.length - 1].partNumber + 1 : 1;
				// 总切片
				const totalChunks = Number(totalSlices);
				console.log(startChunks, '从几个切片开始上传')
				console.log(totalChunks, '总切片数')
				this.fileInfo.progress = Math.floor(startChunks / totalChunks * 100)
				console.log('初始进度', this.fileInfo.progress)
				for (let currentChunk = startChunks; currentChunk <= totalChunks; currentChunk++) {
					const start = (currentChunk - 1) * sliceSize; // 计算出已上传的文件大小
					const end = Math.min(start + sliceSize, file.size); // 计算出当前上传分片结束位置
					// console.log(file, '要分片的文件')
					const blobSlice = file.file.slice(start, end); // 分割文件，截取当前分片文件
					try {
						const params = {
							sliceBlob: blobSlice, // 分片文件
							fileMd5: this.fileInfo.fileHash, // 文件hash值
							partNumber: currentChunk // 分片编号
						}
						const sectionFile = await uploadFileSlice(params)
						this.fileInfo.progress = Math.floor(currentChunk / totalChunks * 100)
						console.log('上传进度', this.fileInfo.progress)
						if (sectionFile.success) {
							console.log(sectionFile, `分片${currentChunk}上传成功`)
						} else {
							console.log(sectionFile, `第${currentChunk}片上传失败`)
							break
						}
						if (currentChunk == totalChunks) {
							// 查询分片文件是否全部上传成功
							const mergeData = await uploadFileMerge(this.fileInfo.fileHash)
							this.$refs.fileProgressPopup.close()
							return mergeData
						}

					} catch (e) {
						console.log('分片上传报错', e)
					}
				}

			},
			async fileSharding(file) {
				const fileInfo = await this.uploadCheck(this.fileInfo)
				console.log(fileInfo, 'fileInfo')
				// uploadStatus：1完成上传  0表示未上传或未完成上传
				if (fileInfo.uploadStatus == 1) {
					// uploadStatus == 1 接口会返回文件的地址以及其他信息， 直接return即可
					return {
						success: true,
						data: fileInfo
					}
				} else {
					// uploadStatus == 0 则需要上传或断点续传
					return this.section(file, fileInfo)
				}

			},
			// 在静态资源服务器初始化文件信息，保存MD5加密信息，用于后续检查文件是否存在或是否上传完成
			async uploadCheck(params) {
				const {
					fileHash,
					fileName,
					fileSize
				} = params;
				let res = await initUploadRegisterRecordApi({
					fileMd5: fileHash,
					fileName,
					fileSize
				});
				// 返回值 uploadStatus：1完成上传  0表示未上传或未完成上传
				return res.data;
			},
			// 整个文件使用spark-md5生成一个唯一id值,这个id改文件名是不会变的，只有改文件内容才会变
			getFileMd5(file) {
				return new Promise((resolve, reject) => {
					const spark = new SparkMD5.ArrayBuffer()
					const fileReader = new FileReader()
					fileReader.onload = (e) => {
						spark.append(e.target?.result)
						resolve(spark.end())
						uni.hideLoading()
					}
					fileReader.onerror = () => {
						reject('生成文件hash值失败')
						uni.hideLoading()
					}
					uni.showLoading({
						title: '文件处理中...'
					})
					fileReader.readAsArrayBuffer(file)
				})
			},
			// 此事件可用于处理文件上传前的效验
			selectFile(e) {
				this.$emit('selectFile', {
					file: e,
					type: 'select-file',
					selectFileFc: (res) => {
						// 处理文件上传自定义事件
						if (res === 'cancel') {
							// 取消上传
							console.log('取消文件上传', e)
							console.log('uni-file-picker文件列表', this.fileData)
							const file = e.tempFiles[0]
							// uni-file-picker选择了文件就会在页面显示，取消上传需要手动删除当前上传的数据
							this.fileData = this.fileData.filter(item => item.uuid != file.uuid)
						} else {
							// 执行上传
							console.log('执行文件上传')
							this.select(e)
						}
					}
				})
			},
			// 获取上传状态
			async select(e) {
				this.fileInfo.file = e.tempFiles[0]
				const uuid = e.tempFiles[0].uuid
				if (!this.fileInfo.file) return; // 文件不存在return
				console.log('选择文件：', this.fileInfo.file)
				this.fileInfo.fileName = this.fileInfo.file.name
				this.fileInfo.fileSize = this.fileInfo.file.size
				// 文件大于阈值，且开启了分片上传
				if (this.fileInfo.file.size > this.maxSize && this.sliceSwitch == 0) {
					console.log('分片')
					// 文件hash值
					this.fileInfo.fileHash = await this.getFileMd5(this.fileInfo.file.file)
					console.log(this.fileInfo.fileHash, "文件生成的hash值")
					if (this.fileInfo.fileHash) {
						const res = await this.fileSharding(this.fileInfo.file)
						console.log('分片上传结果', res)
						if (res.success) {
							console.log('分片上传结果', res)
							res.data.uuid = uuid
							this.$emit('uploadFileSuccess', res)
						} else {
							this.$emit('uploadFileError', res)
						}
					}

				} else {
					console.log('不分片')
					uni.showLoading({
						title: '文件上传中...'
					})
					const res = await uploadFile(this.fileInfo.file.path)
					uni.hideLoading()
					console.log(res, '不分片上传结果')
					if (res.success) {
						res.data.url = res.data.fileUrl
						res.data.uuid = uuid
						this.$emit('uploadFileSuccess', res)
					} else {
						this.$emit('uploadFileError', res)
					}
				}
			},
			// 获取上传进度
			progress(e) {
				console.log('上传进度：', e)
			},

			// 上传成功
			success(e) {
				console.log('上传成功')
			},

			// 上传失败
			fail(e) {
				console.log('上传失败：', e)
			},
			deleteFile(e) {
				console.log('删除文件', e)
				this.$emit('fileRemove', e)
			},
			async getFileConfig() {
				const res = await queryApplicationUploadConfigByAppCodeApi()
				if (res.success) {
					// //服务器配置的单文件最大值（byte）
					this.maxSize = res.data.limitFileSize || 50 * 1024 * 1024;
					this.chunkSize = res.data.sliceSize || 5 * 1024 * 1024
					this.sliceSwitch = res.data.sliceSwitch;
				}
				return res
			}
		}
	}
</script>

<style>
	.progress-box {
		width: calc(100vw - 80rpx);
		background-color: #fff;
		padding: 40rpx 20rpx;
		border-radius: 12rpx;
	}

	/* 文本盒子样式 */
	#text {
		display: flex;
		align-items: center;
		justify-content: center;
	}

	/* 设置动画 */
	@keyframes donghua {
		0 {
			transform: translateY(0rpx);
		}

		20% {
			transform: translateY(-20rpx);
		}

		40%,
		100% {
			transform: translateY(0rpx);
		}
	}

	#text text {
		display: inline-block;
		animation: donghua 2s ease-in-out infinite;
		animation-delay: calc(.1s*var(--i));
		padding: 0 4rpx;
	}
</style>